#ifndef _PCI_H
#define _PCI_H

#include <compiler.h>
#include <ds/list.h>
#include <arch/io.h>

#define PCI_REG_BAR(num) (0x10 + (num - 1) * 4)
#define PCI_REG_STATUS_CMD 0x4

#define PCI_BAR_MMIO(x) (!((x) & 0x1))
#define PCI_BAR_ADDR_MM(x) ((x) & ~0xf)

#define PCI_MSI_ADDR(msi_base) ((msi_base) + 4)
#define PCI_MSI_DATA(msi_base, offset) ((msi_base) + 8 + offset)
#define PCI_MSI_MASK(msi_base, offset) ((msi_base) + 0xC + offset)

#define MSI_CAP_64BIT 0x80
#define MSI_CAP_MASK 0x100
#define MSI_CAP_ENABLE 0x1

#define PCI_CMD_DISABLE_INTR (1 << 10)
#define PCI_CMD_FAST_B2B (1 << 9)
#define PCI_CMD_BUS_MASTER (1 << 2)
#define PCI_CMD_MM_ACCESS (1 << 1)
#define PCI_CMD_IO_ACCESS 1

#define PCI_CONFIG_ADDR 0xCF8
#define PCI_CONFIG_DATA 0xCFC

#define PCI_TYPE_MULTIFUNCTION 0x80
#define PCI_TYPE_DEVICE 0x0
#define PCI_TYPE_PCI_BRIDGE 0x1
#define PCI_TYPE_CARDBUS_BRIDGE 0x2

typedef struct CONFIG_ADDRESS_Register {
    union {
        struct {
            char reg;
            char func_num : 3;
            char dev_num : 5;
            char bus_num : 8;
            char reserved : 7;
            char enable : 1;
        };
        u32 val;
    };
} pci_cfg_addr_reg_t;

typedef struct pci_device {
    lnode_t list;
    u32 device_info;
    u32 class_info;
    pci_cfg_addr_reg_t cspace_base;
    u32 msi_loc;
    u16 intr_info;
} pci_dev_t;

static inline u32 pci_read_cspace(pci_cfg_addr_reg_t data, u32 offset) {
    data.reg = offset;
    outl(PCI_CONFIG_ADDR, data.val);
    return inl(PCI_CONFIG_DATA);
}

static inline void pci_write_cspace(pci_cfg_addr_reg_t data, u32 offset, u32 value) {
    data.reg = offset;
    outl(PCI_CONFIG_ADDR, data.val);
    outl(PCI_CONFIG_DATA, value);
}

void pci_init(void);

pci_dev_t* pci_get_by_class(u32 class);

pci_dev_t* pci_get_by_id(u16 vendorId, u16 deviceId);

size_t pci_bar_sizing(pci_dev_t* dev, u32* bar_out, u32 bar_num);

void pci_setup_msi(pci_dev_t* dev, int vector);

#endif /* _PCI_H */